While most developers typically use the existing generic types within the base class libraries, it is also possible to build your own generic members and custom generic types. Let’s look at how to incorporate custom generics into your own projects. The first step is to build a generic swap method. Begin by creating a new console application named CustomGenericMethods.
When you build custom generic methods, you achieve a supercharged version of traditional method overloading. In Chapter 2, you learned that overloading is the act of defining multiple versions of a single method, which differ by the number of, or type of, parameters.
While overloading is a useful feature in an object oriented language, one problem is that you can easily end up with a ton of methods that essentially do the same thing. For example, assume you need to build some methods that can switch two pieces of data using a simple swap routine. You might begin by authoring a new method that can operate on integers, like this:
// Swap two integers. static void Swap(ref int a, ref int b) { int temp; temp = a; a = b; b = temp; }
So far, so good. But now assume you also need to swap two Person objects; this would require authoring a new version of Swap():
// Swap two Person objects. static void Swap(ref Person a, ref Person b) { Person temp; temp = a; a = b; b = temp; }
No doubt, you can see where this is going. If you also needed to swap floating point numbers, bitmaps, cars, buttons and whatnot, you would have to build even more methods, which would become a maintenance nightmare. You could build a single (non-generic) method that operated on object parameters, but then you face all the issues you examined earlier in this chapter, including boxing, unboxing, a lack of type safety, explicit casting, and so on.
Whenever you have a group of overloaded methods that only differ by incoming arguments, this is your clue that generics could make your life easier. Consider the following generic Swap<T> method that can swap any two Ts:
// This method will swap any two items. // as specified by the type parameter <T>. static void Swap<T>(ref T a, ref T b) { Console.WriteLine("You sent the Swap() method a {0}", typeof(T)); T temp; temp = a; a = b; b = temp; }
Notice how a generic method is defined by specifying the type parameters after the method name, but before the parameter list. Here, you state that the Swap<T>() method can operate on any two parameters of type <T>. To spice things up a bit, you also print out the type name of the supplied placeholder to the console using C#’s typeof() operator. Now consider the following Main() method, which swaps integers and strings:
static void Main(string[] args) { Console.WriteLine("***** Fun with Custom Generic Methods *****\n"); // Swap 2 ints. int a = 10, b = 90; Console.WriteLine("Before swap: {0}, {1}", a, b); Swap<int>(ref a, ref b); Console.WriteLine("After swap: {0}, {1}", a, b); Console.WriteLine(); // Swap 2 strings. string s1 = "Hello", s2 = "There"; Console.WriteLine("Before swap: {0} {1}!", s1, s2); Swap<string>(ref s1, ref s2); Console.WriteLine("After swap: {0} {1}!", s1, s2); Console.ReadLine(); }
The output looks like this:
***** Fun with Custom Generic Methods ***** Before swap: 10, 90 You sent the Swap() method a System.Int32 After swap: 90, 10 Before swap: Hello There! You sent the Swap() method a System.String After swap: There Hello!
The major benefit of this approach is that you have only one version of Swap<T>() to maintain, yet it can operate on any two items of a given type in a type safe manner. Better yet, stack-based items stay on the stack, while heap-based items stay on the heap!
When you invoke generic methods such as Swap<T>, you can optionally omit the type parameter if (and only if) the generic method requires arguments because the compiler can infer the type parameter based on the member parameters. For example, you could swap two System.Boolean values by adding the following code to Main():
// Compiler will infer System.Boolean. bool b1 = true, b2 = false; Console.WriteLine("Before swap: {0}, {1}", b1, b2); Swap(ref b1, ref b2); Console.WriteLine("After swap: {0}, {1}", b1, b2);
Even though the compiler is able to discover the correct type parameter based on the data type used to declare b1 and b2, you should get in the habit of always specifying the type parameter explicitly:
Swap<bool>(ref b1, ref b2);
This makes it clear to your fellow programmers that this method is indeed generic. Moreover, inference of type parameters only works if the generic method has at least one parameter. For example, assume you have the following generic method in your Program class:
static void DisplayBaseClass<T>() { // BaseType is a method used in reflection, // which will be examined in Chapter 15 Console.WriteLine("Base class of {0} is: {1}.", typeof(T), typeof(T).BaseType); }
In this case, you must supply the type parameter upon invocation:
static void Main(string[] args) { ... // Must supply type parameter if // the method does not take params. DisplayBaseClass<int>(); DisplayBaseClass<string>(); // Compiler error! No params? Must supply placeholder! // DisplayBaseClass(); Console.ReadLine(); }
Currently, the generic Swap<T> and DisplayBaseClass<T> methods are defined within the application’s Program class. Of course, as with any method, you are free to define these members in a separate class type (MyGenericMethods) if you would prefer to do it that way:
public static class MyGenericMethods { public static void Swap<T>(ref T a, ref T b) { Console.WriteLine("You sent the Swap() method a {0}", typeof(T)); T temp; temp = a; a = b; b = temp; } public static void DisplayBaseClass<T>() { Console.WriteLine("Base class of {0} is: {1}.", typeof(T), typeof(T).BaseType); } }
The static Swap<T> and DisplayBaseClass<T> methods have been scoped within a new static class type, so you need to specify the type’s name when invoking either member, as in this example:
MyGenericMethods.Swap<int>(ref a, ref b);
Of course, generic methods do not need to be static. If Swap<T> and DisplayBaseClass<T> were instance level (and defined in a non-static class), you would simply make an instance of MyGenericMethods and invoke them using the object variable:
MyGenericMethods c = new MyGenericMethods(); c.Swap<int>(ref a, ref b);
Source Code You can find the CustomGenericMethods project under the Chapter 10 directory.